package t2.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import t2.client.events.StatusUpdateEvent;
import t2.client.events.StatusUpdateEventHandler;
import t2.client.rpc.LocalATcmd;
import t2.client.rpc.LocalATcmdAsync;
import t2.client.rpc.RemoteAtCmd;
import t2.client.rpc.RemoteAtCmdAsync;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;


public class XBeeFormPanel extends FormPanel {

    public static EventBus EVENT_BUS = GWT.create(SimpleEventBus.class);

    final LocalATcmdAsync atCmdService = (LocalATcmdAsync) GWT.create(LocalATcmd.class);
    final RemoteAtCmdAsync remoteAtWebRequestService = (RemoteAtCmdAsync) GWT.create(RemoteAtCmd.class);
    ListBox listBoxNodes = new ListBox();
    final static ListBox listBoxCmds = new ListBox();
    final TextBox cmdTextBox = new TextBox();
    final Grid grid = new Grid(1, 2);
    VerticalPanel verticalPanel = new VerticalPanel();
    AtCmdLabel atCmdLabel = new AtCmdLabel();
    StatusLabel statusLabel = new StatusLabel();

    /* Maintain the master list of AT commands in one place.  The dropdown
     * menu is populated with the items in this list. */
    private static final List<String> atCmds;
    static {
        List<String> cmds = new ArrayList<String>();
        cmds.add("SH");
        cmds.add("SL");
        cmds.add("NI");
        cmds.add("PL");
        cmds.add("MY");
        cmds.add("MP");
        cmds.add("ND");
        cmds.add("NC");
        atCmds = Collections.unmodifiableList(cmds);
    }


    /* Constructor */
    public XBeeFormPanel() {
        super();

        /* Set up for FormPanel usage. */
        setAction(GWT.getModuleBaseURL() + "atCmd"); /* This service will be used on submit. */
        setEncoding(FormPanel.ENCODING_URLENCODED);
        setMethod(FormPanel.METHOD_POST);

        /* This vertical panel will hold the contents. */
        verticalPanel.setSpacing(10);
        setWidget(verticalPanel);

        /* List all the known XBees in this network. */
        populateNodesBox();

        /* List all the available commands. */
        populateCmdBox();

        /* Inform the user what will happen when 'submit' is pressed. */
        verticalPanel.add(atCmdLabel);

        /* Add a submit button. */
        verticalPanel.add(submitButton);

        /* Use a line of text to report back to the user. */
        verticalPanel.add(statusLabel);

        /* Event Bus is used for communication between different elements in this Panel. */
        initializeEventBus();


        /* Process return value from submission here. */
        addSubmitCompleteHandler(new FormPanel.SubmitCompleteHandler() {

            @Override
            public void onSubmitComplete(SubmitCompleteEvent event) {
                Window.alert(event.getResults());
            }
        });

        /* Update the label that informs the user which AT command that will be fired based on 
         * selected items in the drop-down menus. */
        XBeeFormPanel.EVENT_BUS.fireEvent(new StatusUpdateEvent());
    }

    public void initializeEventBus() {
        XBeeFormPanel.EVENT_BUS.addHandler(StatusUpdateEvent.TYPE, new StatusUpdateEventHandler() {

            @Override
            public void updateStatus(StatusUpdateEvent event) {
                String status = "";

                if (cmdTextBox.getText().length() == 0) {
                    status += "Query ";
                    status += listBoxNodes.getItemText(listBoxNodes.getSelectedIndex());
                    status += " for ";
                    status += listBoxCmds.getItemText(listBoxCmds.getSelectedIndex());

                } else {
                    status += "Set ";
                    status += listBoxCmds.getItemText(listBoxCmds.getSelectedIndex());
                    status += " to ";
                    status += cmdTextBox.getText();
                    status += " on ";
                    status += listBoxNodes.getItemText(listBoxNodes.getSelectedIndex());;
                }

                atCmdLabel.getAtCmdLabel().setText(status);
            }
        });
    }


    public void populateCmdBox() {

        /* Check if this has already been done. */
        if (listBoxCmds.getItemCount() > 0) {
            return;
        }

        /* Use a TextBox to handle input parameters for the AT command. */
        cmdTextBox.setName("atCmdTextBox");
        cmdTextBox.setWidth("20");

        /* Populate the command drop-down list with possible AT commands. */
        for (int cmd = 0; cmd < atCmds.size(); cmd++) {
            listBoxCmds.addItem(atCmds.get(cmd));
        }

        /* Give the option to enter parameters.  Add the list 
         * of commands and a text box for parameters.*/
        grid.setWidget(0, 0, listBoxCmds);
        grid.setWidget(0, 1, cmdTextBox);

        verticalPanel.add(grid);

        /* Add form submit event handler. */
        this.addSubmitHandler(new FormPanel.SubmitHandler() {

            @Override
            public void onSubmit(SubmitEvent event) {
                /* This event is fired just before the form is submitted.
                 * Can do validation in here. */
                if (cmdTextBox.getText().length() == 0) {
                    Window.alert("Text must not be empty.");
                    event.cancel();
                }
            }
        });

        cmdTextBox.addKeyUpHandler(new KeyUpHandler() {

            @Override
            public void onKeyUp(KeyUpEvent event) {
                XBeeFormPanel.EVENT_BUS.fireEvent(new StatusUpdateEvent());
            }
        });

        listBoxCmds.addChangeHandler(new ChangeHandler() {

            @Override
            public void onChange(ChangeEvent event) {
                XBeeFormPanel.EVENT_BUS.fireEvent(new StatusUpdateEvent());
            }
        });
    }


    public void populateNodesBox() {

        listBoxNodes.setName("networkList");

        /* Add everything that's been discovered by the ND command in the MainTabPanel. */
        for (int i = 0; i < MainTabPanel.getNetworkMap().size(); i++) {
            listBoxNodes.addItem(MainTabPanel.getNetworkMap().get(MainTabPanel.getAllNodesList().get(i)).get("NI"));
        }

        listBoxNodes.setVisibleItemCount(1);
        verticalPanel.add(listBoxNodes);

        /* Inform the user what will happen if they click 'submit'. */
        listBoxNodes.addChangeHandler(new ChangeHandler() {

            @Override
            public void onChange(ChangeEvent event) {
                XBeeFormPanel.EVENT_BUS.fireEvent(new StatusUpdateEvent());
            }
        });
    }


    Button submitButton = new Button("Submit", new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {

            /* Find serial of Coordinator. */
            final int[] coordSerial = getCoordinatorSerial();

            if (listBoxNodes.getSelectedIndex() == MainTabPanel.getAllNodesList().indexOf(coordSerial)) {
                /* Coordinator */
                if (cmdTextBox.getText().length() == 0) {
                    /* AT query */
                    atCmdService.atcmd(atCmds.get(listBoxCmds.getSelectedIndex()), new AsyncCallback<String>() {

                        @Override
                        public void onFailure(Throwable caught) {}

                        @Override
                        public void onSuccess(String result) {
                            String retval = "";
                            retval += atCmds.get(listBoxCmds.getSelectedIndex());
                            retval += " is ";
                            retval += result;
                            statusLabel.getStatusLabel().setText(retval);
                        }
                    });

                } else {
                    /* AT change value command (not just a query) */
                    RegExp regExp = RegExp.compile("[A-Za-z_ ]+");
                    MatchResult matcher = regExp.exec(cmdTextBox.getText());
                    boolean matchFound = (matcher != null);

                    if (matchFound) {
                        /* String parameter passed in. */
                        atCmdService.atcmd(atCmds.get(listBoxCmds.getSelectedIndex()), cmdTextBox.getText(),
                                new AsyncCallback<String>() {

                                    @Override
                                    public void onFailure(Throwable caught) {}

                                    @Override
                                    public void onSuccess(String result) {
                                        // TODO Preferably fire an AT query
                                        // event
                                        // here and return that value instead.
                                        statusLabel.getStatusLabel().setText(
                                                atCmds.get(listBoxCmds.getSelectedIndex()) + " is now " + result);
                                    }
                                });

                    } else {
                        /* Number argument passed in. */
                        atCmdService.atcmd(atCmds.get(listBoxCmds.getSelectedIndex()),
                                Integer.valueOf(cmdTextBox.getText()), new AsyncCallback<String>() {

                                    @Override
                                    public void onFailure(Throwable caught) {}

                                    @Override
                                    public void onSuccess(String result) {
                                        // TODO Preferably fire an AT query
                                        // event
                                        // here and return that value instead.
                                        statusLabel.getStatusLabel().setText(
                                                atCmds.get(listBoxCmds.getSelectedIndex()) + " is now " + result);
                                    }
                                });
                    }
                }

            } else {
                /* Remote node. */
                int[] serial = getSerialOfSelectedNode();
                if (cmdTextBox.getText().length() == 0) {
                    /* AT query */
                    remoteAtWebRequestService.remoteATquery(serial, atCmds.get(listBoxCmds.getSelectedIndex()),
                            new AsyncCallback<String>() {

                                @Override
                                public void onFailure(Throwable caught) {}

                                @Override
                                public void onSuccess(String result) {
                                    statusLabel.getStatusLabel().setText(
                                            atCmds.get(listBoxCmds.getSelectedIndex()) + " is " + result);
                                }
                            });

                } else {
                    /* AT change value command (not just a query). */
                    RegExp regExp = RegExp.compile("[A-Za-z_ ]+");
                    MatchResult matcher = regExp.exec(cmdTextBox.getText());
                    boolean matchFound = (matcher != null);

                    if (matchFound) {
                        /* String parameter passed in. */
                        remoteAtWebRequestService.remoteATconfig(serial, atCmds.get(listBoxCmds.getSelectedIndex()),
                                cmdTextBox.getText(), new AsyncCallback<String>() {

                                    @Override
                                    public void onFailure(Throwable caught) {}

                                    @Override
                                    public void onSuccess(String result) {
                                        statusLabel.getStatusLabel().setText(
                                                atCmds.get(listBoxCmds.getSelectedIndex()) + " is now " + result);
                                    }
                                });

                    } else {
                        /* Integer parameter passed in. */
                        remoteAtWebRequestService.remoteATconfig(serial, atCmds.get(listBoxCmds.getSelectedIndex()),
                                Integer.valueOf(cmdTextBox.getText()), new AsyncCallback<String>() {

                                    @Override
                                    public void onFailure(Throwable caught) {}

                                    @Override
                                    public void onSuccess(String result) {
                                        statusLabel.getStatusLabel().setText(
                                                atCmds.get(listBoxCmds.getSelectedIndex()) + " is now " + result);
                                    }
                                });
                    }
                }
            }
        }
    });

    public int[] getCoordinatorSerial() {
        int[] serial = new int[] { 0 };

        /* Search through everything until you find the coordinator. */
        Set<Entry<int[], Map<String, String>>> setOuter = MainTabPanel.getNetworkMap().entrySet();
        Iterator<Entry<int[], Map<String, String>>> iterOuter = setOuter.iterator();
        while (iterOuter.hasNext()) {
            Entry<int[], Map<String, String>> entryOuter = iterOuter.next();
            Set<Entry<String, String>> setInner = entryOuter.getValue().entrySet();
            Iterator<Entry<String, String>> iterInner = setInner.iterator();
            while (iterInner.hasNext()) {
                Entry<String, String> entryInner = iterInner.next();
                if (entryInner.getKey().equals("type") && entryInner.getValue().equals("0")) {
                    serial = entryOuter.getKey();
                    return serial;
                }
            }
        }
        return serial;
    }


    public int[] getSerialOfSelectedNode() {
        return MainTabPanel.getAllNodesList().get(listBoxNodes.getSelectedIndex());
    }

}
